// SPDX-License-Identifier: GPL-2.0+
/*
 * Copyright 2014, Staubli Faverges
 * Pierre Aubert
 *
 * eMMC- Replay Protected Memory Block
 * According to JEDEC Standard No. 84-A441
 */
#define pr_fmt(fmt) "mmc-rpmb: " fmt

#include <mci.h>
#include <malloc.h>
#include <dma.h>

int mmc_rpmb_route_frames(struct mci *mci, void *req, unsigned long reqlen,
			  void *rsp, unsigned long rsplen);

/**
 * struct rpmb_frame - rpmb frame as defined by eMMC 5.1 (JESD84-B51)
 *
 * @stuff        : stuff bytes
 * @key_mac      : The authentication key or the message authentication
 *                 code (MAC) depending on the request/response type.
 *                 The MAC will be delivered in the last (or the only)
 *                 block of data.
 * @data         : Data to be written or read by signed access.
 * @nonce        : Random number generated by the host for the requests
 *                 and copied to the response by the RPMB engine.
 * @write_counter: Counter value for the total amount of the successful
 *                 authenticated data write requests made by the host.
 * @addr         : Address of the data to be programmed to or read
 *                 from the RPMB. Address is the serial number of
 *                 the accessed block (half sector 256B).
 * @block_count  : Number of blocks (half sectors, 256B) requested to be
 *                 read/programmed.
 * @result       : Includes information about the status of the write counter
 *                 (valid, expired) and result of the access made to the RPMB.
 * @req_resp     : Defines the type of request and response to/from the memory.
 *
 * The stuff bytes and big-endian properties are modeled to fit to the spec.
 */
struct rpmb_frame {
	u8 stuff[196];
	u8 key_mac[32];
	u8 data[256];
	u8 nonce[16];
	__be32 write_counter;
	__be16 addr;
	__be16 block_count;
	__be16 result;
	__be16 req_resp;
} __packed;

#define RPMB_PROGRAM_KEY	0x1	/* Program RPMB Authentication Key */
#define RPMB_GET_WRITE_COUNTER	0x2	/* Read RPMB write counter */
#define RPMB_WRITE_DATA		0x3	/* Write data to RPMB partition */
#define RPMB_READ_DATA		0x4	/* Read data from RPMB partition */
#define RPMB_RESULT_READ	0x5	/* Read result request  (Internal) */

static int mci_read_write_blocks(struct mci *mci, u32 opcode, int write_flag, void *buf,
				 unsigned int buf_bytes)
{
	int blocks = buf_bytes / 512;
	struct mci_cmd cmd = {
		.cmdidx = opcode,
		.resp_type = MMC_RSP_R1,
	};
	struct mci_data data = {
		.blocks = blocks,
		.blocksize = sizeof(struct rpmb_frame),
	};
	int ret;

	if (write_flag) {
		data.src = buf;
		data.flags = MMC_DATA_WRITE;
	} else {
		data.dest = buf;
		data.flags = MMC_DATA_READ;
	}

	ret = mci_set_blockcount(mci, blocks | (write_flag & MMC_CMD23_ARG_REL_WR));
	if (ret)
		return ret;

	return mci_send_cmd(mci, &cmd, &data);
}

static int rpmb_route_frames(struct mci *mci, void *req,
			     unsigned int req_len, void *resp,
			     unsigned int resp_len)
{
	struct rpmb_frame *frm = req;
	unsigned int cmd_count;
	u16 req_type;
	bool write;
	int ret;

	if (req_len < sizeof(*frm))
		return -EINVAL;

	req_type = be16_to_cpu(frm->req_resp);
	switch (req_type) {
	case RPMB_PROGRAM_KEY:
		if (req_len != sizeof(struct rpmb_frame) ||
		    resp_len != sizeof(struct rpmb_frame))
			return -EINVAL;
		write = true;
		break;
	case RPMB_GET_WRITE_COUNTER:
		if (req_len != sizeof(struct rpmb_frame) ||
		    resp_len != sizeof(struct rpmb_frame))
			return -EINVAL;
		write = false;
		break;
	case RPMB_WRITE_DATA:
		if (req_len % sizeof(struct rpmb_frame) ||
		    resp_len != sizeof(struct rpmb_frame))
			return -EINVAL;
		write = true;
		break;
	case RPMB_READ_DATA:
		if (req_len != sizeof(struct rpmb_frame) ||
		    resp_len % sizeof(struct rpmb_frame))
			return -EINVAL;
		write = false;
		break;
	default:
		return -EINVAL;
	}

	if (write)
		cmd_count = 3;
	else
		cmd_count = 2;

	if (write) {
		struct rpmb_frame *frm = resp;

		/* Send write request frame(s) */
		ret = mci_read_write_blocks(mci, MMC_CMD_WRITE_MULTIPLE_BLOCK,
			  1 | MMC_CMD23_ARG_REL_WR, req, req_len);
		if (ret)
			goto out;

		/* Send result request frame */
		memset(frm, 0, sizeof(*frm));
		frm->req_resp = cpu_to_be16(RPMB_RESULT_READ);
		ret = mci_read_write_blocks(mci, MMC_CMD_WRITE_MULTIPLE_BLOCK, 1,
					    resp, resp_len);
		if (ret)
			goto out;

		/* Read response frame */
		ret = mci_read_write_blocks(mci, MMC_CMD_READ_MULTIPLE_BLOCK, 0,
					    resp, resp_len);
		if (ret)
			goto out;
	} else {
		/* Send write request frame(s) */
		ret = mci_read_write_blocks(mci, MMC_CMD_WRITE_MULTIPLE_BLOCK, 1,
					    req, req_len);
		if (ret)
			goto out;

		/* Read response frame */
		ret = mci_read_write_blocks(mci, MMC_CMD_READ_MULTIPLE_BLOCK, 0,
					    resp, resp_len);
		if (ret)
			goto out;
	}
out:
	return ret;
}

int mci_rpmb_route_frames(struct mci *mci, void *req, unsigned long reqlen,
			  void *rsp, unsigned long rsplen)
{
	/*
	 * Whoever crafted the data supplied to this function knows how to
	 * format the PRMB frames and which response is expected. If
	 * there's some unexpected mismatch it's more helpful to report an
	 * error immediately than trying to guess what was the intention
	 * and possibly just delay an eventual error which will be harder
	 * to track down.
	 */
	void *rpmb_data;
	int ret;

	if (!dma_map_buf_is_aligned(mci->host->hw_dev, rsp, rsplen)) {
		dev_err_once(mci->host->hw_dev, "response or length not DMA aligned\n");
		return -EINVAL;
	}

	mci_blk_part_switch(mci->rpmb_part);

	/* Memory alignment is required by MMC driver */
	rpmb_data = dma_alloc(reqlen);
	if (!rpmb_data)
		return -ENOMEM;

	memcpy(rpmb_data, req, reqlen);

	ret = rpmb_route_frames(mci, rpmb_data, reqlen, rsp, rsplen);

	dma_free(rpmb_data);

	return ret;
}
